package com.lhkj.ct.base.utils;

import com.lhkj.ct.base.enums.ReturnStatus;
import com.lhkj.ct.base.exception.BusinessException;
import org.apache.commons.lang3.StringUtils;
import org.bouncycastle.asn1.gm.GMNamedCurves;
import org.bouncycastle.asn1.x9.X9ECParameters;
import org.bouncycastle.crypto.CipherParameters;
import org.bouncycastle.crypto.engines.SM2Engine;
import org.bouncycastle.crypto.params.ECDomainParameters;
import org.bouncycastle.crypto.params.ECPrivateKeyParameters;
import org.bouncycastle.crypto.params.ECPublicKeyParameters;
import org.bouncycastle.crypto.params.ParametersWithRandom;
import org.bouncycastle.crypto.signers.SM2Signer;
import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPrivateKey;
import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPublicKey;
import org.bouncycastle.jcajce.provider.asymmetric.util.ECUtil;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.math.ec.ECPoint;
import org.bouncycastle.util.encoders.Hex;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.math.BigInteger;
import java.nio.charset.StandardCharsets;
import java.security.*;
import java.security.spec.*;
import java.util.Base64;

/**
 * <p>
 *      加密解密
 * </p>
 *
 * @author job
 * @since 2023/8/27
 */
public class EncryptionUtil {

    private final static Logger logger = LoggerFactory.getLogger(EncryptionUtil.class);

    private static final SM2Engine.Mode DIGEST = SM2Engine.Mode.C1C3C2;

    public static final String privateKey = "00a684d832831d5371d2ff9de6a021a33fb396451e74e0ffe0d415c298b36876fe";

    public static final String publicKey = "04aa909915f87880507e3de515220cc8f82b1c5693f56a0475b3b48ff7448c229734cd724e603000dd78569faa9fbc1eeb93a6d836190a0ee734e2d9e74d804f28";

    /**
     * 创建密钥对
     * @return 密钥对KeyPair
     */
    public static KeyPair initKey() {
        try {
            final ECGenParameterSpec sm2Spec = new ECGenParameterSpec("sm2p256v1");
            // 获取一个椭圆曲线类型的密钥对生成器
            final KeyPairGenerator kpg = KeyPairGenerator.getInstance("EC", new BouncyCastleProvider());
            // 使用SM2参数初始化生成器
            kpg.initialize(sm2Spec);
            // 获取密钥对
            KeyPair keyPair = kpg.generateKeyPair();
            String publicKey = Base64.getEncoder().encodeToString(keyPair.getPublic().getEncoded());
            String privateKey = Base64.getEncoder().encodeToString(keyPair.getPrivate().getEncoded());
//            System.out.println("publicKey:"+ new String(Base64.getMimeDecoder().decode(publicKey)));
//            System.out.println("privateKey:" + new String(Base64.getMimeDecoder().decode(privateKey)));
//            System.out.println("publicKey:" + publicKey);
//            System.out.println("privateKey:" + privateKey);

            // 获取密钥对
            BCECPublicKey p=(BCECPublicKey)keyPair.getPublic();
//            System.out.println("publicKey："+Hex.toHexString(p.getQ().getEncoded(false)));
            BCECPrivateKey s=(BCECPrivateKey)keyPair.getPrivate();
//            System.out.println("privateKey："+Hex.toHexString(s.getD().toByteArray()));
            return keyPair;
        }catch (Exception e) {
            logger.error("generate sm2 key pair failed:{}", e.getMessage(), e);
            throw new BusinessException(ReturnStatus.SC_INTERNAL_SERVER_ERROR, "生成密钥对失败");
        }
    }

    /**
     * 私钥转换为 {@link ECPrivateKeyParameters}
     * @param key key
     * @return
     * @throws InvalidKeyException
     */
    public static ECPrivateKeyParameters privateKeyToParams(String algorithm, byte[] key) throws InvalidKeyException, InvalidKeySpecException, NoSuchAlgorithmException {
        Validate.notNull(key, "key must be not null !");
        PrivateKey privateKey = generatePrivateKey(algorithm, key);
        return (ECPrivateKeyParameters) ECUtil.generatePrivateKeyParameter(privateKey);
    }

    /**
     * 生成私钥
     * @param algorithm 算法
     * @param key       key
     * @return
     */
    public static PrivateKey generatePrivateKey(String algorithm, byte[] key) throws NoSuchAlgorithmException, InvalidKeySpecException {
        Validate.notNull(algorithm, "algorithm must be not null !");
        Validate.notNull(key, "key must be not null !");
        KeySpec keySpec = new PKCS8EncodedKeySpec(key);
        algorithm = getAlgorithmAfterWith(algorithm);
        return getKeyFactory(algorithm).generatePrivate(keySpec);
    }

    /**
     * 公钥转换为 {@link ECPublicKeyParameters}
     * @param key key
     * @return
     * @throws InvalidKeyException
     */
    public static ECPublicKeyParameters publicKeyToParams(String algorithm, byte[] key) throws InvalidKeyException, InvalidKeySpecException, NoSuchAlgorithmException {
        Validate.notNull(key, "key must be not null !");
        PublicKey publicKey = generatePublicKey(algorithm, key);
        return (ECPublicKeyParameters) ECUtil.generatePublicKeyParameter(publicKey);
    }

    /**
     * 生成公钥
     * @param algorithm 算法
     * @param key       key
     * @return
     */
    public static PublicKey generatePublicKey(String algorithm, byte[] key) throws NoSuchAlgorithmException, InvalidKeySpecException {
        Validate.notBlank(algorithm, "algorithm must be not null !");
        Validate.notNull(key, "key must be not null !");
        KeySpec keySpec = new X509EncodedKeySpec(key);
        algorithm = getAlgorithmAfterWith(algorithm);
        return getKeyFactory(algorithm).generatePublic(keySpec);
    }

    /**
     * 获取用于密钥生成的算法<br>
     * 获取XXXwithXXX算法的后半部分算法，如果为ECDSA或SM2，返回算法为EC
     * @param algorithm XXXwithXXX算法
     * @return 算法
     */
    private static String getAlgorithmAfterWith(String algorithm) {
        Validate.notBlank(algorithm, "algorithm must be not null !");
        int indexOfWith = StringUtils.lastIndexOfIgnoreCase(algorithm, "with");
        if (indexOfWith > 0) {
            algorithm = StringUtils.substring(algorithm, indexOfWith + "with".length());
        }
        if ("ECDSA".equalsIgnoreCase(algorithm) || "SM2".equalsIgnoreCase(algorithm)) {
            algorithm = "EC";
        }
        return algorithm;
    }

    /**
     * 获取{@link KeyFactory}
     * @param algorithm 非对称加密算法
     * @return {@link KeyFactory}
     */
    public static KeyFactory getKeyFactory(String algorithm) throws NoSuchAlgorithmException {
        final Provider provider = new BouncyCastleProvider();
        return KeyFactory.getInstance(algorithm, provider);
    }


    /**
     * 加密
     * @param data      数据
     * @param publicKey 公钥
     * @return 加密之后的数据
     */
    public static String encryptString(String data, String publicKey) throws Exception {
        // 获取一条SM2曲线参数
        X9ECParameters sm2ECParameters = GMNamedCurves.getByName("sm2p256v1");
        // 构造ECC算法参数，曲线方程、椭圆曲线G点、大整数N
        ECDomainParameters domainParameters = new ECDomainParameters(sm2ECParameters.getCurve(), sm2ECParameters.getG(), sm2ECParameters.getN());
        //提取公钥点
        ECPoint pukPoint = sm2ECParameters.getCurve().decodePoint(Hex.decode(publicKey));
        // 公钥前面的02或者03表示是压缩公钥，04表示未压缩公钥, 04的时候，可以去掉前面的04
        ECPublicKeyParameters publicKeyParameters = new ECPublicKeyParameters(pukPoint, domainParameters);

        SM2Engine sm2Engine = new SM2Engine(SM2Engine.Mode.C1C3C2);
        // 设置sm2为加密模式
        sm2Engine.init(true, new ParametersWithRandom(publicKeyParameters, new SecureRandom()));

        byte[] arrayOfBytes = null;
        try {
            byte[] in = data.getBytes();
            arrayOfBytes = sm2Engine.processBlock(in, 0, in.length);
        } catch (Exception e) {
            logger.error("SM2加密时出现异常:"+e.getMessage());
        }
        return Hex.toHexString(arrayOfBytes);
    }

    /**
     * 加密
     * @param data      数据
     * @param publicKey 公钥
     * @return 加密之后的数据
     */
    public static String encryptString(String data, byte[] publicKey) throws Exception {
        byte[] bytes = data.getBytes(StandardCharsets.UTF_8);
        return Base64.getMimeEncoder().encodeToString(encrypt(bytes, publicKey));
    }

    /**
     * 加密
     * @param data      数据
     * @param publicKey 公钥
     * @return 加密之后的数据
     */
    public static byte[] encrypt(byte[] data, byte[] publicKey) throws Exception {
        CipherParameters pubKeyParameters = new ParametersWithRandom(publicKeyToParams("SM2", publicKey));
        SM2Engine engine = new SM2Engine(DIGEST);
        engine.init(true, pubKeyParameters);
        return engine.processBlock(data, 0, data.length);
    }

    /**
     * 解密
     * @param cipherData       密文
     * @param privateKey 私钥
     * @return 解密之后的数据
     */
    public static String decryptString(String cipherData, String privateKey) throws Exception {
        // 使用BC库加解密时密文以04开头，传入的密文前面没有04则补上
        if (!cipherData.startsWith("04")){
            cipherData = "04" + cipherData;
        }
        byte[] cipherDataByte = Hex.decode(cipherData);
        BigInteger privateKeyD = new BigInteger(privateKey, 16);
        //获取一条SM2曲线参数
        X9ECParameters sm2ECParameters = GMNamedCurves.getByName("sm2p256v1");
        //构造domain参数
        ECDomainParameters domainParameters = new ECDomainParameters(sm2ECParameters.getCurve(), sm2ECParameters.getG(), sm2ECParameters.getN());
        ECPrivateKeyParameters privateKeyParameters = new ECPrivateKeyParameters(privateKeyD, domainParameters);

        SM2Engine sm2Engine = new SM2Engine(SM2Engine.Mode.C1C3C2);
        // 设置sm2为解密模式
        sm2Engine.init(false, privateKeyParameters);

        String result = "";
        try {
            byte[] arrayOfBytes = sm2Engine.processBlock(cipherDataByte, 0, cipherDataByte.length);
            return new String(arrayOfBytes);
        } catch (Exception e) {
            logger.error("SM2解密时出现异常:"+e.getMessage());
            throw new IllegalArgumentException(e);
        }
//        return result;
    }

    /**
     * 解密
     * @param data       数据
     * @param privateKey 私钥
     * @return 解密之后的数据
     */
    public static String decryptString(String data, byte[] privateKey) throws Exception {
        byte[] decode = Base64.getMimeDecoder().decode(data.getBytes(StandardCharsets.UTF_8));
        return new String(decrypt(decode, privateKey));
    }

    /**
     * 解密
     * @param data       数据
     * @param privateKey 私钥
     * @return 解密之后的数据
     */
    public static byte[] decrypt(byte[] data, byte[] privateKey) throws Exception {
        CipherParameters privateKeyParameters = privateKeyToParams("SM2", privateKey);
        SM2Engine engine = new SM2Engine(DIGEST);
        engine.init(false, privateKeyParameters);
        return engine.processBlock(data, 0, data.length);
    }

    /**
     * 签名
     * @param data 数据
     * @return 签名
     */
    public static byte[] sign(byte[] data, byte[] privateKey) throws Exception {
        SM2Signer signer = new SM2Signer();
        CipherParameters param = new ParametersWithRandom(privateKeyToParams("SM2", privateKey));
        signer.init(true, param);
        signer.update(data, 0, data.length);
        return signer.generateSignature();
    }

    /**
     * 用公钥检验数字签名的合法性
     * @param data      数据
     * @param sign      签名
     * @param publicKey 公钥
     * @return 是否验证通过
     */
    public static boolean verify(byte[] data, byte[] sign, byte[] publicKey) throws Exception {
        SM2Signer signer = new SM2Signer();
        CipherParameters param = publicKeyToParams("SM2", publicKey);
        signer.init(false, param);
        signer.update(data, 0, data.length);
        return signer.verifySignature(sign);
    }

    public static void main(String[] args) throws Exception {

//        KeyPair keyPair = initKey();
//        //明文
//        String plaintext = "为登录";
//
//        // 使用 `Base64` 编码器对字符串进行编码
//        Base64.Encoder encoder = Base64.getEncoder();
////        String encoded = encoder.encodeToString(string.getBytes());
////        System.out.println("Encoded Data: " + encoded);
//
//        // 解码编码数据
//        Base64.Decoder decoder = Base64.getDecoder();
////        String decoded = new String(decoder.decode(encoded));
////        System.out.println("Decoded Data: " + decoded);
//
//        //加密
//        String ciphertext = encoder.encodeToString(encrypt(plaintext.getBytes(StandardCharsets.UTF_8), keyPair.getPublic().getEncoded()));
////                Base64Utils.encode(encrypt(plaintext.getBytes("utf-8"), keyPair.getPublic().getEncoded()));
//        //生成签名
//        String signature = encoder.encodeToString(sign(plaintext.getBytes(StandardCharsets.UTF_8),keyPair.getPrivate().getEncoded()));
////                Base64Utils.encode(sign(plaintext.getBytes("utf-8"),keyPair.getPrivate().getEncoded()));
//        System.out.println("ciphertext: " + encryptString(plaintext, keyPair.getPublic().getEncoded()));
//        System.out.println("signature: " + signature);
//        //解密
//        plaintext = decryptString(ciphertext, keyPair.getPrivate().getEncoded());
////    new String(decrypt(decoder.decode(ciphertext),keyPair.getPrivate().getEncoded()), StandardCharsets.UTF_8);
//        //验签
//        boolean result = verify(plaintext.getBytes(StandardCharsets.UTF_8),decoder.decode(signature),keyPair.getPublic().getEncoded());
//        System.out.println("plaintext: " + plaintext);
//        System.out.println("verify result: " + result);
    }



}
